//
// Copyright (c) 2009 All Right Reserved
//
// vl
//
// 2009-01-01
// Contains ...
using System;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Text;
using System.Xml.Serialization;
using LargoCommon.Abstract;
using LargoCommon.Interfaces;
namespace LargoCommon.Music
{
/// Harmonic interval.
/// Musical Interval represents one interval i.e. acoustic relation of two tones.
/// Some characteristics (continuity, impulse, potential influence, Similarity,..)
/// are assigned to interval.
[Serializable]
[XmlRoot]
public class MusicalInterval : IComparable, IHarmonic
{
#region Constants
/// Natural logarithm of 2.
protected const float Log2 = 0.693147181f;
/// Inverted Logarithm(2).
protected const float InvLog2 = 1.442695041f;
/// Tolerance of continuity.
protected const float ContinuityTolerance = 1.016F;
#endregion
#region Fields
///
/// The formal behavior
///
private readonly BindingBehavior formalBehavior;
///
/// The real behavior
///
private readonly BindingBehavior realBehavior;
/// Formal properties.
private float? frmPotentialInfluence, frmSimilarity;
/// Interval ratio.
private float? ratio;
#endregion
#region Constructors
///
/// Initializes a new instance of the MusicalInterval class. Serializable.
///
public MusicalInterval() {
this.formalBehavior = new BindingBehavior {
Continuity = null,
Impulse = null
};
this.frmPotentialInfluence = null;
this.frmSimilarity = null;
this.realBehavior = new BindingBehavior {
Continuity = null,
Impulse = null
};
}
/// Initializes a new instance of the MusicalInterval class.
/// Harmonic system.
/// Fist element of system.
/// Second element of system.
public MusicalInterval(HarmonicSystem harmonicSystem, byte elementFrom, byte elementTo)
: this() {
Contract.Requires(harmonicSystem != null);
//// if (harmonicSystem == null) { return; }
this.HarmonicSystem = harmonicSystem;
this.HarmonicOrder = harmonicSystem.Order;
this.SystemLength = elementTo - elementFrom;
this.ratio = null;
this.Weight = 1.0f; // SetProperties();
this.FormalLength = GeneralSystem.FormalLength(this.HarmonicOrder, this.SystemLength);
}
/// Initializes a new instance of the MusicalInterval class.
/// Harmonic system.
/// First musical pitch.
/// Second musical pitch.
public MusicalInterval(HarmonicSystem harmonicSystem, MusicalPitch givenPitch1, MusicalPitch givenPitch2)
: this() {
Contract.Requires(harmonicSystem != null);
//// if (harmonicSystem == null) { return; }
this.HarmonicSystem = harmonicSystem;
this.HarmonicOrder = harmonicSystem.Order;
this.ratio = null;
this.SystemLength = givenPitch2?.IntervalFrom(givenPitch1) ?? 0;
this.Weight = 1.0f;
if (givenPitch1 != null) {
this.SystemAltitude = givenPitch1.SystemAltitude;
}
this.FormalLength = GeneralSystem.FormalLength(this.HarmonicOrder, this.SystemLength);
//// SetProperties();
}
/// Initializes a new instance of the MusicalInterval class.
/// Harmonic system.
/// First melodic tone.
/// Second melodic tone.
public MusicalInterval(HarmonicSystem harmonicSystem, MusicalTone tone1, MusicalTone tone2)
: this() {
Contract.Requires(harmonicSystem != null);
Contract.Requires(tone1 != null);
Contract.Requires(tone2 != null);
//// if (harmonicSystem == null) { return; }
//// if (tone1 == null || tone2 == null) {
//// throw new ArgumentException("Tone of interval must not be null."); }
this.SystemLength = 0;
this.HarmonicSystem = harmonicSystem;
this.HarmonicOrder = harmonicSystem.Order;
this.ratio = null;
this.Weight = tone1.Weight + tone2.Weight; //// 0.5f *
//// this.Weight = 1.0f; (when lower time of composing needed)
//// Time optimized
//// MusicalPitch pitch1 = tone1.Pitch;
//// MusicalPitch pitch2 = tone2.Pitch;
if (tone1.Pitch != null) {
//// this.SystemAltitude = tone1.Pitch.SystemAltitude;
if (tone2.Pitch != null) {
//// Time optimized (== pitch2.IntervalFrom(pitch1);)
this.SystemLength = tone2.Pitch.SystemAltitude - tone1.Pitch.SystemAltitude;
//// this.ratio = this.HarmonicSystem.RatioForInterval(this.SysLength);
}
}
this.FormalLength = GeneralSystem.FormalLength(this.HarmonicOrder, this.SystemLength);
//// Time optimization
var formalInterval = this.HarmonicSystem.Intervals[this.FormalLength]; ////GetFormalInterval(this.FormalLength);
if (formalInterval == null) {
return;
}
this.formalBehavior.Continuity = formalInterval.FormalContinuity;
this.formalBehavior.Impulse = formalInterval.FormalImpulse;
this.frmPotentialInfluence = formalInterval.FormalPotentialInfluence;
this.frmSimilarity = formalInterval.FormalSimilarity;
//// SetProperties();
}
#endregion
#region Basic properties
/// Gets inner balance.
/// Property description.
public static float FormalBalance => 1f;
/// Gets or sets ratio of frequencies.
/// Property description.
public HarmonicSystem HarmonicSystem { get; set; }
/// Gets or sets the string of musical symbols.
/// Property description.
[XmlAttribute]
public string ToneSchema { get; set; }
/// Gets or sets ratio of frequencies.
/// Property description.
public byte HarmonicOrder { get; set; }
/// Gets or sets altitude from zero level.
/// Property description.
public int SystemAltitude { get; set; }
/// Gets or sets ratio of frequencies.
/// Property description.
public int SystemLength { get; set; }
/// Gets or sets ratio of frequencies.
/// Property description.
public byte FormalLength { get; set; }
/// Gets or sets ratio of frequencies.
/// Property description.
public float Ratio {
get {
if (this.ratio == null) {
this.ratio = this.HarmonicSystem.RatioForInterval(this.SystemLength);
}
return (float)this.ratio;
}
set => this.ratio = value;
}
/// Gets or sets tone weight (lower tones are heavier).
/// Property description.
public float Weight { get; set; }
/// Gets tone weight (lower tones are heavier).
/// Property description.
public float MeanWeight => this.Weight / 2;
#endregion
#region Formal properties
///
/// Gets or sets the formal energy.
///
/// Property description.
public HarmonicBehavior FormalEnergy { get; set; }
/// Gets value of formal impulse.
/// Property description.
public float FormalImpulse {
get {
if (this.formalBehavior.Impulse == null) {
this.formalBehavior.Impulse = this.FImpulse;
}
return (float)this.formalBehavior.Impulse;
}
}
/// Gets value of formal continuity.
/// Property description.
public float FormalContinuity {
get {
if (this.formalBehavior.Continuity == null) {
this.formalBehavior.Continuity = this.FContinuity;
}
return (float)this.formalBehavior.Continuity;
}
}
/// Gets potential influence of the given interval.
/// Property description.
public float FormalPotentialInfluence {
get {
if (this.frmPotentialInfluence == null) {
this.frmPotentialInfluence = this.FPotentialInfluence;
}
return (float)this.frmPotentialInfluence;
}
}
/// Gets similarity of the given interval.
/// Property description.
public float FormalSimilarity {
get {
if (this.frmSimilarity == null) {
this.frmSimilarity = FSimilarity(this.Ratio);
}
return (float)this.frmSimilarity;
}
}
/// Gets Real impulse.
/// Property description.
/// Returns value.
public float RealImpulse {
get {
if (this.realBehavior.Impulse == null) {
//// 2014/1 was this.FormalImpulse * this.Weight
this.realBehavior.Impulse = this.FormalImpulse * (1 + (this.Weight / 5));
}
return (float)this.realBehavior.Impulse;
}
}
/// Gets Real continuity.
/// Property description.
/// Returns value.
public float RealContinuity {
get {
if (this.realBehavior.Continuity == null) {
//// 2014/1 was this.FormalImpulse * this.Weight
this.realBehavior.Continuity = this.FormalContinuity * (1 + (this.Weight / 5));
}
return (float)this.realBehavior.Continuity;
}
}
#endregion
#region Formal properties
/// Gets value of formal continuity.
/// General musical property.
/// Returns value.
public float FContinuity {
get {
var value = ComputeFContinuity(this.Ratio);
return value;
}
}
/// Gets formal impulse.
/// General musical property.
/// Returns value.
public float FImpulse {
get {
var value = ComputeFImpulse(this.Ratio);
return value;
}
}
/// Gets potential influence of the given interval.
/// General musical property.
/// Returns value.
public float FPotentialInfluence {
get {
var vi = this.FImpulse;
var vc = this.FContinuity;
var value = Math.Abs(vc) - (DefaultValue.HalfUnit * vi);
return value;
}
}
#endregion
#region Static operators
//// TICS rule 7@526: Reference types should not override the equality operator (==)
//// public static bool operator ==(MusicalInterval interval1, MusicalInterval interval2) { return object.Equals(interval1, interval2); }
//// public static bool operator !=(MusicalInterval interval1, MusicalInterval interval2) { return !object.Equals(interval1, interval2); }
//// but TICS rule 7@530: Class implements interface 'IComparable' but does not implement '==' and '!='.
///
/// Implements the operator <.
///
/// The interval1.
/// The interval2.
///
/// Returns value.
///
public static bool operator <(MusicalInterval interval1, MusicalInterval interval2) {
if (interval1 != null && interval2 != null) {
return interval1.SystemAltitude < interval2.SystemAltitude;
}
return false;
}
///
/// Implements the operator >.
///
/// The interval1.
/// The interval2.
///
/// Returns value.
///
public static bool operator >(MusicalInterval interval1, MusicalInterval interval2) {
if (interval1 != null && interval2 != null) {
return interval1.SystemAltitude > interval2.SystemAltitude;
}
return false;
}
///
/// Implements the operator <=.
///
/// The interval1.
/// The interval2.
///
/// Returns value.
///
public static bool operator <=(MusicalInterval interval1, MusicalInterval interval2) {
if (interval1 != null && interval2 != null) {
return interval1.SystemAltitude <= interval2.SystemAltitude;
}
return false;
}
///
/// Implements the operator >=.
///
/// The interval1.
/// The interval2.
///
/// Returns value.
///
public static bool operator >=(MusicalInterval interval1, MusicalInterval interval2) {
if (interval1 != null && interval2 != null) {
return interval1.SystemAltitude >= interval2.SystemAltitude;
}
return false;
}
#endregion
#region Static methods - abstract
/// Logarithmic value of the formal interval ratio.
/// Interval ratio.
/// Returns value.
public static float FormalLog(float ratio) {
var slog = (float)(Math.Log(ratio) * InvLog2);
var flog = slog - (float)Math.Floor(slog);
return flog;
}
/// Value of the formal interval ratio, e.g. 1.5 for the quint.
/// Interval ratio.
/// Returns value.
public static float FormalRatio(float ratio) {
var flog = FormalLog(ratio);
var fr = (float)Math.Pow(2, flog);
return fr;
}
/// Logarithmic value of the smaller formal interval, e.g. quart not quint.
/// Interval ratio.
/// Returns value.
public static float SharpLog(float ratio) {
var flog = FormalLog(ratio);
var sharpLog = Math.Min(flog, 1 - flog);
return sharpLog;
}
/// Value of the smaller formal interval, e.g. quart not quint.
/// Interval ratio.
/// Returns value.
public static float SharpRatio(float ratio) {
var sharpLog = SharpLog(ratio);
var sharpRatio = (float)Math.Pow(2, sharpLog);
return sharpRatio;
}
/// Returns Similarity of the given interval.
/// Interval ratio.
/// Returns value.
public static float FSimilarity(float ratio) {
const float tolerance = 0.05f;
var fr = FormalRatio(ratio);
float value = Math.Abs(fr - 1) < tolerance ? +1 : 0;
return value;
}
/// Returns Similarity of the given interval.
/// Formal distance.
/// Returns value.
public static float GuessSonanceValue(int formalDistance) {
const float influenceOfFifths = 0.1f;
const float influenceOfThirds = 0.1f;
const float influenceOfSeconds = -0.3f;
const float influenceOfHalftones = -0.5f;
//// The tones consonant with other tones have higher values
float value = 0;
formalDistance = formalDistance > short.MinValue ? Math.Abs(formalDistance) : short.MinValue;
if (formalDistance == (byte)IntervalType.Fourth || formalDistance == (byte)IntervalType.Fifth) {
value += influenceOfFifths;
}
if (formalDistance == (byte)IntervalType.MinorThird || formalDistance == (byte)IntervalType.MajorThird) {
value += influenceOfThirds;
}
if (formalDistance == (byte)IntervalType.Second) {
value += influenceOfSeconds;
}
if (formalDistance == (byte)IntervalType.Halftone) {
value += influenceOfHalftones;
}
return value;
}
#endregion
#region Static methods - formal properties
/// Formal impulse.
/// Interval ratio.
/// Returns value.
public static float ComputeFImpulse(float ratio) {
const float tolerance = 0.01f;
const float fi1 = 12.0f; //// (float)(22.17f / Math.Exp(24 * SharpLog((float)(1.05946))));
const float definedBase = 22.17f;
const float definedMultiple = 24.0f;
//// 2006/06 - simplified, float mRatio = medianRatio(ratio);
var sharpLog = SharpLog(ratio);
//// float r = (float)(24.0*Math.Log(mRatio)/this.Log2);
var r = definedMultiple * sharpLog;
var expr = Math.Exp(r);
var result = !MathSupport.EqualNumbers((float)expr, 0.00f, tolerance) ? (float)(definedBase * r * r / expr) : 0f;
var formalValue = (float)Math.Round((result / fi1) * 100.0f, 3);
return formalValue < 100f ? formalValue : 100f;
}
/// Value of formal continuity.
/// Interval ratio.
/// Returns value.
public static float ComputeFContinuity(float ratio) {
const float ratioC1 = 1.500000F;
const float inverseC1 = 1.333333F;
const float ratioC2 = 1.250000F;
const float inverseC2 = 1.600000F;
const float ratioC3 = 1.750000F;
const float inverseC3 = 1.142857F;
var fr = FormalRatio(ratio);
var formalValue = MathSupport.EqualNumbersRational(fr, ratioC1, ContinuityTolerance) ? -HarmonicSystem.C1 : 0.0F;
if (MathSupport.EqualNumbersRational(fr, inverseC1, ContinuityTolerance)) { // 4.0F/3
formalValue = HarmonicSystem.C1;
}
else {
if (MathSupport.EqualNumbersRational(fr, ratioC2, ContinuityTolerance)) { // 5.0F/4
formalValue = -HarmonicSystem.C2;
}
else {
if (MathSupport.EqualNumbersRational(fr, inverseC2, ContinuityTolerance)) { // 8.0F/5
formalValue = HarmonicSystem.C2;
}
else {
if (MathSupport.EqualNumbersRational(fr, ratioC3, ContinuityTolerance)) { // 7.0F/4
formalValue = -HarmonicSystem.C3;
}
else {
if (MathSupport.EqualNumbersRational(fr, inverseC3, ContinuityTolerance)) { // 8.0F/7
formalValue = HarmonicSystem.C3;
}
}
}
}
}
return formalValue / HarmonicSystem.C1 * 100.0F;
}
/// Returns potential influence of the given interval.
/// Interval ratio.
/// Returns value.
public static float ComputeFPotentialInfluence(float ratio) {
var vi = ComputeFImpulse(ratio); //// see also FImpulse
var vc = ComputeFContinuity(ratio); //// see also FContinuity
var value = Math.Abs(vc) - (DefaultValue.HalfUnit * vi);
return value;
}
#endregion
#region Comparison
/// Support of sorting according to interval ratio.
/// Object to be compared.
/// Returns value.
public int CompareTo(object obj) {
//// This kills the DataGrid
//// throw new ArgumentException("Object is not a MusicalInterval");
return obj is MusicalInterval mi ? this.Ratio.CompareTo(mi.Ratio) : 0;
}
/// Test of equality.
/// Object to be compared.
/// Returns value.
public override bool Equals(object obj) {
//// check null (this pointer is never null in C# methods)
if (object.ReferenceEquals(obj, null)) {
return false;
}
if (object.ReferenceEquals(this, obj)) {
return true;
}
if (this.GetType() != obj.GetType()) {
return false;
}
return this.CompareTo(obj) == 0;
}
/// Support of comparison.
/// Returns value.
public override int GetHashCode() {
return this.Ratio.GetHashCode();
}
#endregion
#region Public methods
///
/// Value Of Property.
///
/// Musical property.
/// Returns value.
public float ValueOfProperty(GenProperty property) {
float value;
switch (property) {
case GenProperty.InnerContinuity:
value = this.FormalContinuity;
break;
case GenProperty.InnerImpulse:
value = this.FormalImpulse;
break;
case GenProperty.FormalPotentialInfluence:
value = this.FormalPotentialInfluence;
break;
case GenProperty.RealContinuity:
value = this.RealContinuity;
break;
case GenProperty.RealImpulse:
value = this.RealImpulse;
break;
default:
value = 0;
break;
}
return value;
}
#endregion
#region String representation
/// String representation of the object.
/// Returns value.
public override string ToString() {
var s = new StringBuilder();
s.Append(
$"{string.Format(CultureInfo.CurrentCulture.NumberFormat, "{0,6}", this.Ratio)} ({string.Format(CultureInfo.CurrentCulture.NumberFormat, "{0,6}", this.Weight)})\t");
//// s.Append(this.StringOfProperties());
return s.ToString();
}
#endregion
}
}